#include "TriCell.h"

float TriCell::getMaxHeight() const {
    return maxHeight;
}

float TriCell::getMinHeight() const {
    return minHeight;
}

Vector3f TriCell::n_s(float relx, float rely) const {
    Vector3f x_s0 = (1 - rely) * n_s00 + rely * n_s01;
    Vector3f x_s1 = (1 - rely) * n_s10 + rely * n_s11;
    Vector3f result = (1 - relx) * x_s0 + relx * x_s1;
    result.normalize();
    return result;
}

Intersection TriCell::traceNegative(Ray& ray) const {
    Vector3f diff = ray.origin - Vector3f(x, y, z00);
    float dist = diff * n0;
    float d = (n0 * ray.dir);
    if (d < 0.0) {
        float t = -dist / d;
        if (ray.isInRange(t)) {
            Vector3f hit = ray.point(t);
            float relx = hit.x - x;
            float rely = hit.y - y;

            if (relx >= 0.0 && rely >= 0.0 && relx + rely <= 1.0) return Intersection(t, hit, n0, n_s(relx, rely));
        }
    }
    return Intersection();
}
Intersection TriCell::tracePositive(Ray& ray) const {
    Vector3f diff = ray.origin - Vector3f(x + 1, y + 1, z11);
    float dist = diff * n1;
    float d = (n1 * ray.dir);
    if (d < 0.0) {
        float t = -dist / d;
        if (ray.isInRange(t)) {
            Vector3f hit = ray.point(t);
            float relx = hit.x - x;
            float rely = hit.y - y;

            if (relx <= 1.0 && rely <= 1.0 && relx + rely >= 1.0) return Intersection(t, hit, n1, n_s(relx, rely));
        }
    }
    return Intersection();
}

Intersection TriCell::traceRay(Ray& ray) const {
    if (ray.dir.x + ray.dir.y > 0.0) {
        Intersection result = traceNegative(ray);
        if (result.exists()) return result;
        return tracePositive(ray);
    } else {
        Intersection result = tracePositive(ray);
        if (result.exists()) return result;
        return traceNegative(ray);
    }
}

Intersection TriCell::traceNegativeSlope(Ray& ray) const {
    float dz = ray.dir.z - ray.dir.x * dx0 - ray.dir.y * dy0;
    if (dz > 0) return Intersection();

    float diffz = z00 - ray.origin.z;
    float t = diffz / dz;
    if (t > 0) {
        Vector3f hit = ray.point(t);
        float relx = hit.x - x;
        float rely = hit.y - y;

        if (relx >= 0.0 && rely >= 0.0 && relx + rely <= 1.0) return Intersection(t, hit, n0, n_s(relx, rely));
    }
    return Intersection();
}

Intersection TriCell::tracePositiveSlope(Ray& ray) const {
    float dz = ray.dir.z - ray.dir.x * dx1 - ray.dir.y * dy1;
    if (dz > 0) return Intersection();

    float diffz = z11 - ray.origin.z;
    float t = diffz / dz;
    if (t > 0) {
        Vector3f hit = ray.point(t);
        float relx = hit.x - x;
        float rely = hit.y - y;

        if (relx <= 1.0 && rely <= 1.0 && relx + rely >= 1.0) return Intersection(t, hit, n1, n_s(relx, rely));
    }
    return Intersection();
}

Intersection TriCell::traceSlopeRay(Ray& slopeRay) const {
    if (slopeRay.dir.x + slopeRay.dir.y > 0.0) {
        Intersection result = traceNegativeSlope(slopeRay);
        if (result.exists()) return result;
        return tracePositiveSlope(slopeRay);
    } else {
        Intersection result = tracePositiveSlope(slopeRay);
        if (result.exists()) return result;
        return traceNegativeSlope(slopeRay);
    }
}

bool TriCell::traceShadowRay(Ray& ray) const {
    //TODO: optimize?
    return traceRay(ray).exists();
}

void TriCell::setFromHeightmap(Heightmap& heightmap, int x, int y) {
    this->x = x;
    this->y = y;
    z00 = heightmap.at(x, y);
    maxHeight = z00;
    minHeight = z00;

    z10 = heightmap.at(x + 1, y);
    if (z10 > maxHeight) maxHeight = z10;
    else if (z10 < minHeight) minHeight = z10;

    z01 = heightmap.at(x, y + 1);
    if (z01 > maxHeight) maxHeight = z01;
    else if (z01 < minHeight) minHeight = z01;

    z11 = heightmap.at(x + 1, y + 1);
    if (z11 > maxHeight) maxHeight = z11;
    else if (z11 < minHeight) minHeight = z11;

    dx0 = z10 - z00;
    dx1 = z11 - z01;
    dy0 = z01 - z00;
    dy1 = z11 - z10;

    Vector3f x0 = Vector3f(1, 0, dx0);
    Vector3f y0 = Vector3f(0, 1, dy0);
    Vector3f x1 = Vector3f(1, 0, dx1);
    Vector3f y1 = Vector3f(0, 1, dy1);

    n0 = x0 ^ y0;
    n0.normalize();
    n1 = x1 ^ y1;
    n1.normalize();

    //shading normals
    float dx, dy;
    Vector3f vx, vy;

    //n_s00
    if (x > 0) {
        dx = (heightmap.at(x + 1, y) - heightmap.at(x - 1, y)) / 2.0;
    } else {
        dx = dx0;
    }
    if (y > 0) {
        dy = (heightmap.at(x, y + 1) - heightmap.at(x, y - 1)) / 2.0;
    } else {
        dy = dy0;
    }
    vx = Vector3f(1, 0, dx);
    vy = Vector3f(0, 1, dy);
    n_s00 = vx ^ vy;
    n_s00.normalize();

    //n_s10
    if (x < heightmap.width() - 2) {
        dx = (heightmap.at(x + 2, y) - heightmap.at(x, y)) / 2.0;
    } else {
        dx = dx0;
    }
    if (y > 0) {
        dy = (heightmap.at(x + 1, y + 1) - heightmap.at(x + 1, y - 1)) / 2.0;
    } else {
        dy = dy1;
    }
    vx = Vector3f(1, 0, dx);
    vy = Vector3f(0, 1, dy);
    n_s10 = vx ^ vy;
    n_s10.normalize();

    //n_s01
    if (x > 0) {
        dx = (heightmap.at(x + 1, y + 1) - heightmap.at(x - 1, y + 1)) / 2.0;
    } else {
        dx = dx1;
    }
    if (y < heightmap.height() - 2) {
        dy = (heightmap.at(x, y + 2) - heightmap.at(x, y)) / 2.0;
    } else {
        dy = dy0;
    }
    vx = Vector3f(1, 0, dx);
    vy = Vector3f(0, 1, dy);
    n_s01 = vx ^ vy;
    n_s01.normalize();

    //n_s11
    if (x < heightmap.width() - 2) {
        dx = (heightmap.at(x + 2, y + 1) - heightmap.at(x, y + 1)) / 2.0;
    } else {
        dx = dx1;
    }
    if (y < heightmap.height() - 2) {
        dy = (heightmap.at(x + 1, y + 2) - heightmap.at(x + 1, y)) / 2.0;
    } else {
        dy = dy1;
    }
    vx = Vector3f(1, 0, dx);
    vy = Vector3f(0, 1, dy);
    n_s11 = vx ^ vy;
    n_s11.normalize();
}
